/************************************************************
* @file gkt_malloc.c
************************************************************/
#include <string.h>

#include "gkt_debug.h"
#include "gkt_board.h"
#include "gkt_malloc.h"

#define MALLOC_ALIGNMENT	8
#define MALLOC_ALIGNMASK	(MALLOC_ALIGNMENT - 1)
#define memory_alloc_size_align(size)	\
	(((size) + MALLOC_ALIGNMASK) & (~MALLOC_ALIGNMASK))

#if GKT_MALLOC_FREE_RECORD_ENABLE

#if defined(GKT_CONFIG_DEBUG_MALLOC_FREE_RECORD_MAXNUMS) \
	&& (GKT_CONFIG_DEBUG_MALLOC_FREE_RECORD_MAXNUMS > 0)
#define MFR_MAXNUMS	GKT_CONFIG_DEBUG_MALLOC_FREE_RECORD_MAXNUMS
#else
#define MFR_MAXNUMS	256
#endif

#define MFR_ADDR_LO_BITS	16
#define MFR_ADDR_LO_MASK	((1U << MFR_ADDR_LO_BITS) - 1)
#define MFR_BLK_UNITS_BITS	14
#define MFR_TYPE_BITS	2
#define MFR_CALLER_BITS	20
#define MFR_SEQ_BITS	12
#define MFR_SEQ_MAX		((1U << MFR_SEQ_BITS) - 1)

#define MFR_CALLER_ADDR_HI	0x01000000

enum {
	MFR_TYPE_ALLOC = 0,
	MFR_TYPE_FREE,
	MFR_TYPE_REALLOC_FREE,
	MFR_TYPE_MAX
};

typedef struct _malloc_free_record {
	uint32_t	addr_lo : MFR_ADDR_LO_BITS;	// 0xX_xxxx	(2 <= X < 7)		16-bits
	uint32_t	block_units : MFR_BLK_UNITS_BITS;	// 0x0_Xxxx	(0 <= X < 8)		16-bits
	uint32_t	type : MFR_TYPE_BITS;
	uint32_t	caller : MFR_CALLER_BITS;	// 0x010x_xxxx	20-bits
	uint32_t	seq : MFR_SEQ_BITS;		// 0x0_0xxx	12-bits
} malloc_free_record_s;

typedef struct _malloc_free {
	uint32_t	seq;
	uint32_t	nums;
	malloc_free_record_s	records[MFR_MAXNUMS];
	uint32_t	records_bits[(MFR_MAXNUMS + 0x1f) >> 5];
} malloc_free_s;
static malloc_free_s s_memory_af;

static void malloc_free_record(uint32_t type,
				uint32_t block_addr, uint32_t size,  
				uint32_t caller_addr)
{
	malloc_free_record_s *record;
	uint32_t addr_lo, block_units, caller_lo;
	uint32_t i, j, index, seq, mask;
	int fatal_occurs;
	
	if (block_addr 
		&& ((block_addr < GKT_SRAM_BASEADDR) 
			|| (block_addr >= (GKT_SRAM_BASEADDR + GKT_SRAM_SIZE))
			|| (block_addr & MALLOC_ALIGNMASK)))
	{
		gkt_fatal("malloc_free: (%u) block(0x%x) invalid - caller(0x%x)\n", 
			type, block_addr, caller_addr);
		return;
	}

	if (!s_memory_af.seq)
		s_memory_af.seq = 1;

	addr_lo = (block_addr & 0x7fff8) >> 3;
	caller_lo = caller_addr & 0xfffff;
	index = ~0;
	fatal_occurs = 0;
	record = NULL;

	if (MFR_TYPE_ALLOC == type) {
		block_units = memory_alloc_size_align(size) >> 3;

		/* find and check in allocated records */
		for (i = 0; i < s_memory_af.nums; i++) {
			record = &s_memory_af.records[i];

			if (MFR_TYPE_ALLOC == record->type) {	/* used / not freed block */
				if ((record->addr_lo == addr_lo)
					|| ((addr_lo < record->addr_lo) 
						&& ((addr_lo + block_units + 1) > record->addr_lo))
					|| ((addr_lo > record->addr_lo) 
						&& (addr_lo < (record->addr_lo + record->block_units)))) 
				{
					fatal_occurs = 1;
					gkt_fatal("malloc: block(0x%x) exist or overlap - caller(0x%x)\n", 
						block_addr, caller_addr);
					gkt_fatal("malloc: exist block(0x%x) size(0x%x) caller(0x%x)\n",
						(uint32_t)s_memory_af.records[i].addr_lo << 3,
						(uint32_t)s_memory_af.records[i].block_units << 3,
						MFR_CALLER_ADDR_HI | (uint32_t)s_memory_af.records[i].caller);
				}
			}
		}

		if (fatal_occurs) {
			gkt_malloc_free_show_record("AllocFatal");
			return;
		}

		if (s_memory_af.nums < MFR_MAXNUMS)
			index = s_memory_af.nums++;
		else {
			seq = ~0;
			index = MFR_MAXNUMS;

			for (i = 0; i < MFR_MAXNUMS; i++) {
				if ((s_memory_af.records[i].type != MFR_TYPE_ALLOC)
					&& (s_memory_af.records[i].seq < seq)) 
				{
					index = i;
					seq = s_memory_af.records[i].seq;
				}
			}

			if (index == MFR_MAXNUMS) {
				gkt_info("malloc: no freed record.\n");
				gkt_info("malloc: block(0x%x) size(0x%x) caller(0x%x)\n", 
					block_addr, size, caller_addr);
				gkt_malloc_free_show_record("NoRecord");
				return;
			}
		}			

		record = &s_memory_af.records[index];
		record->addr_lo = addr_lo;
		record->block_units = block_units;
	}
	else {
		/* find in allocated records */
		for (i = 0; i < s_memory_af.nums; i++) {
			record = &s_memory_af.records[i];
			if (MFR_TYPE_ALLOC == record->type) {
				if ((uint32_t)record->addr_lo == addr_lo)
					index = i;
				else if ((addr_lo > (uint32_t)record->addr_lo) 
						&& (addr_lo < (uint32_t)(record->addr_lo + record->block_units)))
				{
					fatal_occurs = 1;
					gkt_fatal("free: block(0x%x) unalign - caller(0x%x)\n", 
						block_addr, caller_addr);
					gkt_fatal("free: exist - block(0x%x) size(0x%x) caller(0x%x)\n",
						(uint32_t)record->addr_lo << 3, 
						(uint32_t)record->block_units << 3,
						MFR_CALLER_ADDR_HI | (uint32_t)record->caller);
				}
			}
		}

		if (index >= s_memory_af.nums) {
			fatal_occurs = 1;
			gkt_info("free: no matched record in allocated ones.\n");
			gkt_info("free: block(0x%x), size(0x%x), caller(0x%x)\n", 
				block_addr, size, caller_addr);

			/* find matched record in already freed ones */
			for (i = 0; i < s_memory_af.nums; i++) {
				record = &s_memory_af.records[i];
				if (MFR_TYPE_ALLOC == record->type)
					continue;

				if ((uint32_t)record->addr_lo == addr_lo) {
					gkt_fatal("free: block(0x%x) repeat - caller(0x%x)\n", 
						block_addr, caller_addr);
					gkt_fatal("free: (%u) already freed - size(0x%x) caller(0x%x)\n",
						record->type, (uint32_t)record->block_units << 3,
						MFR_CALLER_ADDR_HI | (uint32_t)record->caller);
				}
				else if ((addr_lo > record->addr_lo) 
						&& (addr_lo < (record->addr_lo + record->block_units)))
				{
					gkt_fatal("free: block(0x%x) unalign - caller(0x%x)\n", 
						block_addr, caller_addr);
					gkt_fatal("free: (%u) already freed - block(0x%x) size(0x%x) caller(0x%x)\n",
						record->type, (uint32_t)record->addr_lo << 3,
						(uint32_t)record->block_units << 3,
						MFR_CALLER_ADDR_HI | (uint32_t)record->caller);
				}
			}
		}

		if (fatal_occurs) {
			gkt_malloc_free_show_record("FreeFatal");
			return;
		}

		record = &s_memory_af.records[index];
	}



	record->type = type;
	record->caller = caller_lo;
	record->seq = s_memory_af.seq;

	s_memory_af.seq++;
	if (s_memory_af.seq > MFR_SEQ_MAX) {
		s_memory_af.seq = 1;

		memset(s_memory_af.records_bits, 0, (MFR_MAXNUMS + 0x07) >> 3);
		for (i = 0; i < s_memory_af.nums; i++) {
			//gkt_info("MFR: re-assign seq ...\n");

			seq = ~0;
			for (j = 0; j < s_memory_af.nums; j++) {
				mask = 1U << (j & 0x1f);
				if (!(s_memory_af.records_bits[j >> 5] & mask)) {
					if (s_memory_af.records[j].seq < seq) {
						index = j;
						seq = s_memory_af.records[j].seq;
					}
				}
			}

			s_memory_af.records[index].seq = s_memory_af.seq++;
			s_memory_af.records_bits[index >> 5] |= 1U << (index & 0x1f);
		}
	}
}

static const char *sc_mfr_type_strs[MFR_TYPE_MAX] = {
	"USED", "FREE", "FREE"
};
void gkt_malloc_free_show_record(const char *location)
{
	malloc_free_record_s *record;
	uint32_t i, j, index, seq, mask;

	gkt_printf("\n");
	gkt_printf("========================================\n");
	gkt_printf("=== MALLOC FREE RECORD - %s\n", location ? location : "");
	gkt_printf("----------------------------------------\n");

	memset(s_memory_af.records_bits, 0, (MFR_MAXNUMS + 0x07) >> 3);
	for (i = 0; i < s_memory_af.nums; i++) {
		index = 0;
		seq = ~0;
		for (j = 0; j < s_memory_af.nums; j++) {
			mask = 1U << (j & 0x1f);
			if (!(s_memory_af.records_bits[j >> 5] & mask)) {
				if (s_memory_af.records[j].seq < seq) {
					index = j;
					seq = s_memory_af.records[j].seq;
				}
			}
		}
		s_memory_af.records_bits[index >> 5] |= 1U << (index & 0x1f);

		record = &s_memory_af.records[index];
		gkt_printf("RECORD: [%s] ptr(0x%05x) size(0x%05x) callrer(0x%08x)\n", 
			sc_mfr_type_strs[record->type],
			record->addr_lo << 3, record->block_units << 3,
			MFR_CALLER_ADDR_HI | (uint32_t)record->caller);
	}
	gkt_printf("========================================\n");
}

#else
#define malloc_free_record(type, block_addr, size, caller_addr)	\
	do { gkt_unused(caller_addr); } while(0)
#endif

#if !defined(GKT_CONFIG_STDLIB_MALLOC) || !GKT_CONFIG_STDLIB_MALLOC

#if defined(GKT_CONFIG_OS_ENABLE) && GKT_CONFIG_OS_ENABLE \
	&& defined(GKT_CONFIG_OS_FREERTOS) && GKT_CONFIG_OS_FREERTOS
#include "FreeRTOS.h"

#define MFR_GET_CALLER(caller)	\
	do { __asm volatile ("mov %0, lr	\n"	: "=r" (caller)); } while (0)

void *malloc(size_t block_size)
{
	uint32_t caller;
	void *block_ptr = NULL;

	MFR_GET_CALLER(caller);

	GKT_ASSERT(block_size > 0);

	if (block_size > 0)
		block_ptr = pvPortMalloc(block_size);
	malloc_free_record(MFR_TYPE_ALLOC, (uint32_t)block_ptr, block_size, caller);

	return block_ptr;
}

void *calloc(size_t n, size_t size)
{
	uint32_t caller;
	size_t block_size;
	void *block_ptr = NULL;

	MFR_GET_CALLER(caller);
	
	block_size = n * size;
	GKT_ASSERT(block_size > 0);

	if (block_size > 0) {
		block_ptr = pvPortMalloc(block_size);
		if (block_ptr)
			memset(block_ptr, 0, block_size);
	}

	malloc_free_record(MFR_TYPE_ALLOC, (uint32_t)block_ptr, block_size, caller);

	return block_ptr;
}

void *realloc(void *ptr, size_t size)
{
	uint32_t caller;
	void *block_ptr = NULL;

	MFR_GET_CALLER(caller);

	GKT_ASSERT(ptr != NULL);
	GKT_ASSERT(size > 0);

	malloc_free_record(MFR_TYPE_REALLOC_FREE, (uint32_t)ptr, 0, caller);
	if (ptr)
		vPortFree(ptr);

	if (size > 0)
		block_ptr = pvPortMalloc(size);
	malloc_free_record(MFR_TYPE_ALLOC, (uint32_t)block_ptr, size, caller);

	return block_ptr;
}

void free(void *block_ptr)
{
	uint32_t caller;

	MFR_GET_CALLER(caller);

	GKT_ASSERT(block_ptr != NULL);

	malloc_free_record(MFR_TYPE_FREE, (uint32_t)block_ptr, 0, caller);
	if (block_ptr)
		vPortFree(block_ptr);
}
#else
/* not implement */
void *malloc(size_t block_size)
{
	GKT_ASSERT(0);
	return NULL;
}

void *calloc(size_t n, size_t size)
{
	GKT_ASSERT(0);
	return NULL;
}

void *realloc(void *ptr, size_t size)
{
	GKT_ASSERT(0);
	return NULL;
}

void free(void *block_ptr)
{
	GKT_ASSERT(0);
}
#endif

#endif	/* !GKT_CONFIG_STDLIB_MALLOC */

